#ifndef PW_PBL_BITENUM_HPP
#define PW_PBL_BITENUM_HPP

#include <type_traits>
#include <utility>
#include "BitInt.hpp"
#include "Math.hpp"

namespace pw_pbl
{
	/** enum which support _BitInt(N) as underlying type
	sample:
	```
		struct AA : BitEnum<AA, 1>
		{
			using BitEnum::BitEnum;
			enum EEnum : uint8_t
			{
				kk,
				cc
			};
		};
	```
	*/
	template <typename IMP, auto...>
	struct BitEnum;

	template <typename SELF>
	struct BitEnum<SELF>
	{
	private:
		constexpr BitEnum() noexcept = default;
		template <typename, auto...>
		friend struct BitEnum;
	};

	template <typename SELF, size_t BITS>
	struct BitEnum<SELF, BITS> : BitEnum<SELF>
	{
		using underlying_type = UBitInt<BITS>;

		underlying_type underlying;

		constexpr auto enum_value(this SELF self) { return typename SELF::EEnum{self.underlying}; }

		constexpr bool operator==(const BitEnum &e) const { return underlying == e.underlying; }
		constexpr bool operator!=(auto e) const { return !(*this == e); }

		constexpr BitEnum() noexcept : underlying(0) {}
		constexpr BitEnum(const BitEnum &e) noexcept = default;
		explicit constexpr BitEnum(underlying_type v) noexcept : underlying(v) {}

		template <typename ENUM>
		constexpr BitEnum(ENUM e) noexcept requires std::same_as<ENUM, typename SELF::EEnum> : underlying(std::to_underlying(e)) {}

		constexpr BitEnum &operator=(const BitEnum &e) noexcept = default;
	};

	template <typename SELF, size_t BITS, size_t COUNT, auto MIN, auto MAX>
	struct BitEnum<SELF, BITS, COUNT, MIN, MAX> : BitEnum<SELF, BITS>
	{
		static_assert(MIN <= MAX);
		static constexpr size_t ECount = COUNT;
		static constexpr bool ENatural = MIN == 0 && MAX == COUNT - 1;

		using BitEnum<SELF, BITS>::BitEnum;

		template <typename ELE>
		struct MapArray
		{
			ELE raw[ECount];

			template <typename SUB>
			constexpr decltype(auto) operator[](this SUB &&sub, BitEnum id) requires ENatural { return std::forward_like<SUB>(sub.raw[id.underlying]); }
		};
	};

	template <typename T>
	concept ccBitEnum = std::derived_from<T, BitEnum<T>>;

	template <typename T>
	concept ccDuckEnum = std::is_enum_v<T> || ccBitEnum<T>;

	template <typename T>
	struct DuckEnumTraits;

	template <typename T> requires std::is_enum_v<T>
	struct DuckEnumTraits<T>
	{
		using underlying = std::underlying_type_t<T>;
		using enum_type = T;
	};

	template <ccBitEnum T>
	struct DuckEnumTraits<T>
	{
		using underlying = T::underlying_type;
		using enum_type = T::EEnum;
	};

	template <typename T>
	using underlying_type_t = DuckEnumTraits<T>::underlying;

	template <typename E>
	constexpr std::underlying_type_t<E> to_underlying(E e) { return std::to_underlying(e); }

	template <ccBitEnum T>
	constexpr T::underlying_type to_underlying(T e) { return e.underlying; }

} // namespace pw_pbl

namespace pw_pbl::Math
{
	template <ccBitEnum T>
	constexpr size_t BitCntOf<T> = BitCntOf<typename T::underlying_type>;
}

#endif // PW_PBL_BITENUM_HPP
